package com.ositano.flutter_3des;

import java.security.Key;
import java.security.spec.AlgorithmParameterSpec;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.security.spec.KeySpec;
import java.nio.charset.StandardCharsets;

public class Flutter3desPluginJava {

    private static final String ALGORITHM = "DESede/CBC/PKCS5Padding";
    private static final String ALGORITHM_3DES = "DESede/CBC/PKCS5Padding";
    private static final String KEY_INSTANCE = "DESede";
    private static final byte[] DIGITS = new byte['f' + 1];

    private static final char[] HEX_DIGITS = new char[]{
            '0', '1', '2', '3', '4', '5', '6', '7',
            '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
    };

    private static final char[] FIRST_CHAR = new char[256];
    private static final char[] SECOND_CHAR = new char[256];

    static {
        for (int i = 0; i <= 'F'; i++) {
            DIGITS[i] = -1;
        }
        for (byte i = 0; i < 10; i++) {
            DIGITS['0' + i] = i;
        }
        for (byte i = 0; i < 6; i++) {
            DIGITS['A' + i] = (byte) (10 + i);
            DIGITS['a' + i] = (byte) (10 + i);
        }

        for (int i = 0; i < 256; i++) {
            FIRST_CHAR[i] = HEX_DIGITS[(i >> 4) & 0xF];
            SECOND_CHAR[i] = HEX_DIGITS[i & 0xF];
        }
    }

    /**
     * Encrypt
     */
    public static String encryptToHex(String originStr, String secretKey, String iv) {
//        return byte2hex(encrypt(originStr, secretKey, iv));
        try {
            Cipher cipher = encryptCipher(secretKey.getBytes(), iv.getBytes());
            return encodeHex(cipher.doFinal(originStr.getBytes(StandardCharsets.UTF_8)),true);
        }catch (Exception e){
            //log.error("3des加密异常：{}", e.getMessage());
            return null;
        }
    }

    /**
     * Decrypt
     */
    public static String decryptFromHex(String encryptHexStr, String secretKey, String iv) {
        if(encryptHexStr == null || iv == null)
            return null;
//        try {
//            return decrypt(hex2byte(encryptHexStr.getBytes()), secretKey, iv);
//        } catch (Exception e){
//            e.printStackTrace();
//            return "";
//        }
        try {
            Cipher cipher = decryptCipher(secretKey.getBytes(), iv.getBytes());
            return new String(cipher.doFinal(decodeHex(encryptHexStr)), StandardCharsets.UTF_8);
        }catch (Exception e){
            //log.error("3des解密异常：{}", e.getMessage());
            return null;
        }
    }
    private static Cipher encryptCipher(byte[] key, byte[] iv) throws Exception{
        return initCipher("DESede", Cipher.ENCRYPT_MODE, key, iv);
    }
    private static Cipher decryptCipher(byte[] key, byte[] iv) throws Exception{
        return initCipher("DESede", Cipher.DECRYPT_MODE, key, iv);
    }
    private static Cipher initCipher(String algorithm, int mode, byte[] key, byte[] iv) throws Exception{
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        KeySpec keySpec = new DESedeKeySpec(key);
        SecretKey secretKey = SecretKeyFactory.getInstance(algorithm).generateSecret(keySpec);
        cipher.init(mode, secretKey, new IvParameterSpec(iv));
        return cipher;
    }
    /**
     * 3DES
     *
     * @param data string to be encrypted
     * @param key  can't be less than 8 bits
     * @param iv  vector (usually 8 bits)
     * @return return byte array
     */
    public static byte[] encrypt(String data, String key, String iv) {
        if(data == null || iv == null)
            return null;
        try{

            byte[] keyByteArray = hexStringToByteArray(key);
            byte[] ivByteArray = hexStringToByteArray(iv);

            DESedeKeySpec dks = new DESedeKeySpec(keyByteArray);
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KEY_INSTANCE);
            Key secretKey = keyFactory.generateSecret(dks);
            Cipher cipher = Cipher.getInstance(ALGORITHM_3DES);

            AlgorithmParameterSpec paramSpec = new IvParameterSpec(ivByteArray);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey,paramSpec);
            return cipher.doFinal(data.getBytes());
        }catch(Exception e){
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 3DES
     *
     * @param data to be decrypted
     * @param key  can't be less than 8 bits
     * @param iv  vector (usually 8 bits)
     * @return return String
     */
    public static String decrypt(byte[] data, String key, String iv) {
        if(data == null || iv == null)
            return null;
        try {

            byte[] keyByteArray = hexStringToByteArray(key);
            byte[] ivByteArray = hexStringToByteArray(iv);

            DESedeKeySpec dks = new DESedeKeySpec(keyByteArray);
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KEY_INSTANCE);
            Key secretKey = keyFactory.generateSecret(dks);
            Cipher cipher = Cipher.getInstance(ALGORITHM_3DES);
            AlgorithmParameterSpec paramSpec = new IvParameterSpec(ivByteArray);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, paramSpec);
            return new String(cipher.doFinal(data));
        } catch (Exception e){
            e.printStackTrace();
            return "";
        }
    }

    /**
     *
     * Two-line conversion string
     */
    private static String byte2hex(byte[] b) {
        StringBuilder hs = new StringBuilder();
        String stmp;
        for (int n = 0; b!=null && n < b.length; n++) {
            stmp = Integer.toHexString(b[n] & 0XFF);
            if (stmp.length() == 1)
                hs.append('0');
            hs.append(stmp);
        }
        return hs.toString().toUpperCase();
    }

    private static byte[] hex2byte(byte[] b) {
        if((b.length%2)!=0)
            throw new IllegalArgumentException();
        byte[] b2 = new byte[b.length/2];
        for (int n = 0; n < b.length; n+=2) {
            String item = new String(b,n,2);
            b2[n/2] = (byte)Integer.parseInt(item,16);
        }
        return b2;
    }

    //transform string data (010203) to {0x01, 0x02, 0x03} or {1, 2, 3}
    public static byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                    + Character.digit(s.charAt(i+1), 16));
        }
        return data;
    }
    public static byte[] decodeHex(String hexString) {
        int length = hexString.length();

        if ((length & 0x01) != 0) {
            throw new IllegalArgumentException("Odd number of characters.");
        }

        boolean badHex = false;
        byte[] out = new byte[length >> 1];
        for (int i = 0, j = 0; j < length; i++) {
            int c1 = hexString.charAt(j++);
            if (c1 > 'f') {
                badHex = true;
                break;
            }

            final byte d1 = DIGITS[c1];
            if (d1 == -1) {
                badHex = true;
                break;
            }

            int c2 = hexString.charAt(j++);
            if (c2 > 'f') {
                badHex = true;
                break;
            }

            final byte d2 = DIGITS[c2];
            if (d2 == -1) {
                badHex = true;
                break;
            }

            out[i] = (byte) (d1 << 4 | d2);
        }

        if (badHex) {
            throw new IllegalArgumentException("Invalid hexadecimal digit: " + hexString);
        }

        return out;
    }
    public static String encodeHex(byte[] array, boolean zeroTerminated) {
        char[] cArray = new char[array.length * 2];

        int j = 0;
        for (int i = 0; i < array.length; i++) {
            int index = array[i] & 0xFF;
            if (index == 0 && zeroTerminated) {
                break;
            }

            cArray[j++] = FIRST_CHAR[index];
            cArray[j++] = SECOND_CHAR[index];
        }

        return new String(cArray, 0, j);
    }
}
